﻿using DotNetOpenAuth.AspNet;
using Microsoft.Web.WebPages.OAuth;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using WebMatrix.WebData;

namespace z.Controllers {
	[z.Filters.InitializeSimpleMembership]
	public class UserController : Controller {
		public ActionResult Index() {
			return View();
		}
		#region Adminter
		[Authorize(Roles = "Admin")]
		public ActionResult Administer() {
			return View("Table");
		}
		//[Authorize(Roles = "Admin")]
		//[HttpPost]
		//public ActionResult _Adminster(z.m.db.UserProfile model) {
		//	return View(model);
		//}
		[Authorize(Roles = "Admin")]
		[HttpPost]
		public ActionResult _Edit(m.db.UserProfile In) {
			var Out = App.db.UserProfiles.Where(x => x.UserId == In.UserId).Single();
			Out.UserName = In.UserName;
			Out.First_Name = In.First_Name;
			Out.Last_Name = In.Last_Name;
			Out.CurrentRole = In.CurrentRole;
			Out.Phone = In.Phone;
			if (In.UserId <= 3) ModelState.Clear();
			App.db.SaveChanges();
			return PartialView("_Row", Out);
		}
		#endregion Adminter
		#region Local Account
		public ActionResult Login(string returnUrl) {
			ViewBag.ReturnUrl = returnUrl;

			return View();
		}
		[HttpPost]
		public ActionResult Login(m.Login model, string returnUrl) {
			if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe)) {
				return RedirectToLocal(returnUrl);
			}

			// If we got this far, something failed, redisplay form
			ModelState.AddModelError("", "The user name or password provided is incorrect.");
			return View(model);
		}
		[HttpPost]
		[ValidateAntiForgeryToken]
		[Authorize]
		public ActionResult LogOff() {
			WebSecurity.Logout();
			return RedirectToAction("Index", "Home");
		}
		public ActionResult Register() {
			return View();
		}
		[HttpPost]
		[ValidateAntiForgeryToken]
		public ActionResult Register(m.PasswordnProfile model) {
			var str = model.Captcha == null ? "" : model.Captcha.ToUpper();
			if (App.db.UserProfiles.Where(x => x.UserName == model.UserName).Count() != 0) {
				ModelState["UserName"].Errors.Add("User Name ["+model.UserName+"] not avalible");
			}
			if (ModelState.IsValid && (Session["captcha"] as string == str)) {
				// Attempt to register the user
				try {
					WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
					var u = App.db.UserProfiles.Where(x => x.UserName == model.UserName).Single();
					u.First_Name = model.First_Name;
					u.Last_Name = model.Last_Name;
					u.Phone = model.Phone;
					u.Address = model.Address;
					App.db.SaveChanges();
					WebSecurity.Login(model.UserName, model.Password);
					return RedirectToAction("Index", "Home");
				} catch (MembershipCreateUserException e) {
					ModelState.AddModelError("", e.Message);
				}
			} else if (Session["captcha"] as string != str) {
				ModelState["Captcha"].Errors.Add("Captcha Not macth character in picture");
			}

			// If we got this far, something failed, redisplay form
			return View(model);
		}
		[HttpPost]
		[ValidateAntiForgeryToken]
		[Authorize]
		public ActionResult Disassociate(string provider, string providerUserId) {
			string ownerAccount = OAuthWebSecurity.GetUserName(provider, providerUserId);
			ManageMessageId? message = null;

			// Only disassociate the account if the currently logged in user is the owner
			if (ownerAccount == User.Identity.Name) {
				// Use a transaction to prevent the user from deleting their last login credential
				using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.Serializable })) {
					bool hasLocalAccount = OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(User.Identity.Name));
					if (hasLocalAccount || OAuthWebSecurity.GetAccountsFromUserName(User.Identity.Name).Count > 1) {
						OAuthWebSecurity.DeleteAccount(provider, providerUserId);
						scope.Complete();
						message = ManageMessageId.RemoveLoginSuccess;
					}
				}
			}
			return RedirectToAction("Manage", new { Message = message });
		}
		[Authorize]
		public ActionResult Manage(ManageMessageId? message) {
			ViewBag.StatusMessage =
				 message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
				 : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
				 : message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed."
				 : "";
			//ViewBag.HasLocalPassword = OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(User.Identity.Name));
			ViewBag.ReturnUrl = Url.Action("Manage");
			return View();
		}
		[Authorize]
		[HttpPost]
		[ValidateAntiForgeryToken]
		public ActionResult Manage(m.LocalPassword model) {
			bool hasLocalAccount = OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(User.Identity.Name));
			ViewBag.HasLocalPassword = hasLocalAccount;
			ViewBag.ReturnUrl = Url.Action("Manage");
			if (hasLocalAccount) {
				if (ModelState.IsValid) {
					// ChangePassword will throw an exception rather than return false in certain failure scenarios.
					bool changePasswordSucceeded = false;
					try {
						changePasswordSucceeded = WebSecurity.ChangePassword(User.Identity.Name, model.OldPassword, model.Password);
					} catch (Exception) {
						changePasswordSucceeded = false;
					}

					if (changePasswordSucceeded) {
						return RedirectToAction("Manage", new { Message = ManageMessageId.ChangePasswordSuccess });
					} else {
						ModelState.AddModelError("OldPassword", "The current password is incorrect or the new password is invalid.");
					}
				}
			} else {
				// User does not have a local password so remove any validation errors caused by a missing
				// OldPassword field
				ModelState state = ModelState["OldPassword"];
				if (state != null) {
					state.Errors.Clear();
				}

				if (ModelState.IsValid) {
					try {
						WebSecurity.CreateAccount(User.Identity.Name, model.Password);
						return RedirectToAction("Manage", new { Message = ManageMessageId.SetPasswordSuccess });
					} catch (Exception) {
						ModelState.AddModelError("", String.Format("Unable to create local account. An account with the name \"{0}\" may already exist.", User.Identity.Name));
					}
				}
			}

			// If we got this far, something failed, redisplay form
			return View(model);
		}
		[Authorize]
		public ActionResult Profile() {
			var Out = App.db.UserProfiles.Where(x => x.UserName == User.Identity.Name).Single();
			return View(Out);
		}
		[Authorize]
		[HttpPost]
		public ActionResult Profile(m.db.UserProfile In) {
			var Out = App.db.UserProfiles.Where(x => x.UserName == User.Identity.Name).Single();
			Out.First_Name = In.First_Name;
			Out.Last_Name = In.Last_Name;
			Out.Phone = In.Phone;
			Out.Address = In.Address;
			//ModelState["UserName"].Errors.Clear();
			if (ModelState.IsValid) {
				try {
					App.db.SaveChanges();
				} catch (Exception ex) {
				}
			}
			return View(Out);
		}
		public enum ManageMessageId {
			ChangePasswordSuccess,
			SetPasswordSuccess,
			RemoveLoginSuccess,
		}
		#endregion Local Account
		#region External
		[AllowAnonymous]
		[ChildActionOnly]
		public ActionResult _ExternalLoginsList(string returnUrl) {
			ViewBag.ReturnUrl = returnUrl;
			return PartialView("_ExternalLoginsList", OAuthWebSecurity.RegisteredClientData);
		}
		[HttpPost]
		[AllowAnonymous]
		[ValidateAntiForgeryToken]
		public ActionResult ExternalLogin(string provider, string returnUrl) {
			return new OAuthLoginResult(provider, Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
		}
		[AllowAnonymous]
		public ActionResult ExternalLoginCallback(string returnUrl) {
			AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
			if (!result.IsSuccessful) {
				return RedirectToAction("ExternalLoginFailure");
			}

			if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false)) {
				return RedirectToLocal(returnUrl);
			}

			if (User.Identity.IsAuthenticated) {
				// If the current user is logged in add the new account
				OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name);
				return RedirectToLocal(returnUrl);
			} else {
				// User is new, ask for their desired membership name
				string loginData = OAuthWebSecurity.SerializeProviderUserId(result.Provider, result.ProviderUserId);
				ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName;
				ViewBag.ReturnUrl = returnUrl;
				return View("ExternalLoginConfirmation", new m.RegisterExternalLogin { UserName = result.UserName, ExternalLoginData = loginData });
			}
		}
		[HttpPost]
		[AllowAnonymous]
		[ValidateAntiForgeryToken]
		public ActionResult ExternalLoginConfirmation(m.RegisterExternalLogin model, string returnUrl) {
			string provider = null;
			string providerUserId = null;

			if (User.Identity.IsAuthenticated || !OAuthWebSecurity.TryDeserializeProviderUserId(model.ExternalLoginData, out provider, out providerUserId)) {
				return RedirectToAction("Manage");
			}

			if (ModelState.IsValid) {
				// Insert a new user into the database
				using (m.db.Database db = new m.db.Database()) {
					m.db.UserProfile user = db.UserProfiles.FirstOrDefault(u => u.UserName.ToLower() == model.UserName.ToLower());
					// Check if user already exists
					if (user == null) {
						// Insert name into the profile table
						db.UserProfiles.Add(
							new m.db.UserProfile {
								UserName = model.UserName
							});
						db.SaveChanges();
						OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName);
						OAuthWebSecurity.Login(provider, providerUserId, createPersistentCookie: false);
						return RedirectToLocal(returnUrl);
					} else {
						ModelState.AddModelError("UserName", "User name already exists. Please enter a different user name.");
					}
				}
			}
			ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(provider).DisplayName;
			ViewBag.ReturnUrl = returnUrl;
			return View(model);
		}
		[AllowAnonymous]
		public ActionResult ExternalLoginFailure() {
			return View();
		}
		internal class ExternalLoginResult : ActionResult {
			public ExternalLoginResult(string provider, string returnUrl) {
				Provider = provider;
				ReturnUrl = returnUrl;
			}

			public string Provider { get; private set; }
			public string ReturnUrl { get; private set; }

			public override void ExecuteResult(ControllerContext context) {
				OAuthWebSecurity.RequestAuthentication(Provider, ReturnUrl);
			}
		}
		internal class OAuthLoginResult : ActionResult {
			public OAuthLoginResult(string provider, string returnUrl) {
				Provider = provider;
				ReturnUrl = returnUrl;
			}

			public string Provider { get; private set; }
			public string ReturnUrl { get; private set; }

			public override void ExecuteResult(ControllerContext context) {
				OAuthWebSecurity.RequestAuthentication(Provider, ReturnUrl);
			}
		}
		[ChildActionOnly]
		public ActionResult _RemoveExternalLogins() {
			ICollection<OAuthAccount> accounts = OAuthWebSecurity.GetAccountsFromUserName(User.Identity.Name);
			List<m.ExternalLogin> externalLogins = new List<m.ExternalLogin>();
			foreach (OAuthAccount account in accounts) {
				AuthenticationClientData clientData = OAuthWebSecurity.GetOAuthClientData(account.Provider);

				externalLogins.Add(new m.ExternalLogin {
					Provider = account.Provider,
					ProviderDisplayName = clientData.DisplayName,
					ProviderUserId = account.ProviderUserId,
				});
			}

			ViewBag.ShowRemoveButton = externalLogins.Count > 1 || OAuthWebSecurity.HasLocalAccount(WebSecurity.GetUserId(User.Identity.Name));
			return PartialView("_RemoveExternalLogins", externalLogins);
		}
		#endregion External
		private ActionResult RedirectToLocal(string returnUrl) {
			if (Url.IsLocalUrl(returnUrl)) {
				return Redirect(returnUrl);
			} else {
				return RedirectToAction("Index", "Home");
			}
		}
	}
}
